// -*- c++ -*-
//
// bookmarkapi.cpp
//
// Copyright (C) 2005 Petter Stokke <ummo@hellokitty.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
//

#include "bookmarkapi.h"

#include <qobjectlist.h>
#include <qpixmapcache.h>

#include <klocale.h>
#include <kdebug.h>
#include <kglobal.h>
#include <kiconloader.h>



// --- BookmarkAPI ---

BookmarkAPI::BookmarkAPI( QObject* parent, const char* name )
        : QObject( parent, name )
{
    m_user = i18n( "unknown user", "Unknown" );
    if ( !m_dcop.attach() )
        kdDebug() << "Failed to attach to DCOP server." << endl;
}

const QString& BookmarkAPI::user() const
{
    return m_user;
}

void BookmarkAPI::setUser( const QString& _user )
{
    if ( m_user != _user ) {
        m_user = _user;
        emit userChanged( m_user );
    }
}

QPixmap* BookmarkAPI::faviconForURL( const KURL& _url )
{
    if ( !m_dcop.isAttached() )
        return 0;

    QString iconName;
    QByteArray data, replyData;
    QCString replyType;
    QDataStream arg( data, IO_WriteOnly );
    arg << _url;
    if ( !m_dcop.call( "kded", "favicons", "iconForURL(KURL)", data, replyType, replyData, true, -1 ) ) {
        kdDebug() << "Failed to perform DCOP call kded::favicons::iconForURL(\"" << _url.prettyURL() << "\")" << endl;
        return 0;
    }
    if ( replyType != "QString" ) {
        kdDebug() << "DCOP call received \"" << replyType << " return value, expected QString." << endl;
        return 0;
    }
    QDataStream rv( replyData, IO_ReadOnly );
    rv >> iconName;
    if ( iconName.isEmpty() )
        return 0;

    QString cIconName = QString( "favicon:" ) + iconName;
    QPixmap* cIcon = QPixmapCache::find( cIconName );
    if ( cIcon )
        return cIcon;

    KIconLoader* icons = KGlobal::iconLoader();
    QPixmap icon = icons->loadIcon( iconName, KIcon::Small );
    QPixmapCache::insert( cIconName, icon );
    cIcon = QPixmapCache::find( cIconName );
    return cIcon;
}




// --- BookmarkAPIJob ---

BookmarkAPIJob::BookmarkAPIJob( BookmarkAPI* parent, const char* name )
        : QObject( parent, name )
{}



// --- Collection ---

Collection::Collection( BookmarkAPI* _api, QObject* parent, const char* name )
        : QObject( parent, name )
{
    m_posts.clear();
    m_api = _api;
}

BookmarkAPI* Collection::api()
{
    return m_api;
}

void Collection::clear()
{
    m_posts.setAutoDelete(true);
    m_posts.clear();
    m_posts.setAutoDelete(false);
}

void Collection::addPost( Post* _post )
{
    if ( _post->parent() )
        _post->parent() ->removeChild( _post );
    insertChild( _post );
    m_posts.insert( _post->url().url(), _post );
}

void Collection::addPosts( const QPtrList<Post>& _posts )
{
    QPtrListIterator<Post> it( _posts );
    for ( ; it.current(); ++it )
        addPost( it.current() );
}

bool Collection::deletePost( Post* _post )
{
    if ( _post->parent() != this )
        return false;
    m_posts.remove( _post->url().url() );
    delete _post;
    return true;
}

bool Collection::deletePost( const KURL& _url )
{
    Post * p = m_posts.find( _url.url() );
    if ( p )
        return deletePost( p );
    return false;
}

QStringList Collection::tags() const
{
    // TODO: Index this data so this function can be a little less inefficient plzkthx
    QMap<QString, int> t;
    QStringList::const_iterator tit;
    QObjectListIterator it( *( children() ) );
    for ( ; it.current(); ++it ) {
        Post* p = dynamic_cast<Post*>( it.current() );
        if ( p )
            for ( tit = p->tags().begin(); tit != p->tags().end(); ++tit )
                t.replace( *tit, 0 );
    }
    return QStringList( t.keys() );
}

QPtrList<Post> Collection::postsWithTags( const QStringList& _tags ) const
{
    QPtrList<Post> l;
    QObjectListIterator it( *( children() ) );
    for ( ; it.current(); ++it ) {
        Post* p = dynamic_cast<Post*>( it.current() );
        if ( p && p->hasTags( _tags ) )
            l.append( p );
    }
    return l;
}

QPtrList<Post> Collection::postsMatchingQuery( const QString& _query ) const
{
    QPtrList<Post> l;
    QObjectListIterator it( *( children() ) );
    for ( ; it.current(); ++it ) {
        Post* p = dynamic_cast<Post*>( it.current() );
        if ( p && p->matchesQuery( _query ) )
            l.append( p );
    }
    return l;
}

void Collection::renameTag( const QString& oldTag, const QString& newTag )
{
    QObjectListIterator it( *( children() ) );
    for ( ; it.current(); ++it ) {
        Post* p = dynamic_cast<Post*>( it.current() );
        if ( p )
            p->renameTag( oldTag, newTag );
    }
}

uint Collection::postCount() const
{
    return m_posts.count();
}

uint Collection::tagCount() const
{
    // TODO: Should be a query for the tag index, see TODO for Collection::tags()
    return tags().count();
}

uint Collection::tagCount( const QStringList& _tags ) const
{
    // TODO: Should be a query for the tag index, see TODO for Collection::tags()
    uint c = 0;
    QDictIterator<Post> it( m_posts );
    for ( ; it.current(); ++it )
        if ( it.current() ->hasTags( _tags ) )
            c++;
    return c;
}

bool Collection::loadData( QDataStream& cache )
{
    uint ct;
    cache >> ct;
    if ( ct != 0x1337 )
        return false;
    cache >> m_lastUpdate;
    cache >> ct;

    m_posts.clear();
    for ( ; ct; ct-- ) {
        Post* post = new Post();
        cache >> *post;
        addPost( post );
    }
    return true;
}

bool Collection::saveData( QDataStream& cache )
{
    cache << ( uint ) 0x1337;
    cache << m_lastUpdate;
    cache << ( uint ) m_posts.count();
    QDictIterator<Post> it( m_posts );
    for ( ; it.current(); ++it )
        cache << *( it.current() );
    return true;
}

const QDateTime& Collection::lastUpdate() const
{
    return m_lastUpdate;
}

void Collection::setLastUpdate( const QDateTime& _time )
{
    m_lastUpdate = _time;
}



// --- Post ---

Post::Post( QObject* parent, const char* name )
        : QObject( parent, name )
{}

Post::Post( const QString& title, const QString& description, const KURL& url, const QStringList& tags, const QString& user, QObject* parent, const char* name )
        : QObject( parent, name )
        , m_title( title )
        , m_description( description )
        , m_url( url )
        , m_tags( tags )
        , m_user( user )
{}

Collection* Post::collection()
{
    Collection * c = dynamic_cast<Collection*>( parent() );
    return c;
}

const QString& Post::title() const
{
    return m_title;
}

void Post::setTitle( const QString& _title )
{
    m_title = _title;
}

const QString& Post::description() const
{
    return m_description;
}

void Post::setDescription( const QString& _description )
{
    m_description = _description;
}

const KURL& Post::url() const
{
    return m_url;
}

void Post::setURL( const KURL& _url )
{
    m_url = _url;
}

const QStringList& Post::tags() const
{
    return m_tags;
}

void Post::setTags( const QStringList& _tags )
{
    m_tags = _tags;
}

const QString& Post::user() const
{
    return m_user;
}

void Post::setUser( const QString& _user )
{
    m_user = _user;
}

bool Post::hasTag( const QString& _tag ) const
{
    return m_tags.contains( _tag );
}

bool Post::hasTags( const QStringList& _tags ) const
{
    QStringList::const_iterator it;
    for ( it = _tags.begin(); it != _tags.end(); ++it )
        if ( !hasTag( *it ) )
            return false;
    return true;
}

void Post::renameTag( const QString& oldTag, const QString& newTag )
{
    if ( !hasTag( oldTag ) )
        return ;
    int pos = m_tags.findIndex( oldTag );
    m_tags[ pos ] = newTag;
}

void Post::addTag( const QString& _tag )
{
    if ( !hasTag( _tag ) )
        m_tags.append( _tag );
}

bool Post::removeTag( const QString& _tag )
{
    return m_tags.remove( _tag );
}

bool Post::matchesQuery( const QString& _query ) const
{
    QStringList queries( QStringList::split( " ", _query ) );
    if ( !queries.count() )
        return false;
    // ct is total number of terms in the query, hits is number of terms that matched.
    // So ct == hits means the query so far is a match.
    int ct = 0, hits = 0;
    QStringList::iterator it;
    for ( it = queries.begin(); it != queries.end(); ++it ) {
        if ( *it == "AND" )
            continue; // AND is implicit.
        if ( *it == "OR" ) {
            // Construct a new query with the remaining items and return a logical OR of the results of both.
            QStringList nq;
            for ( ++it; it != queries.end(); ++it )
                nq << *it;
            return hits == ct || matchesQuery( nq.join( " " ) );
        }
        // Match terms from the query with the data fields.
        ct++;
        if ( m_title.lower().contains( ( *it ).lower() )
                || m_description.lower().contains( ( *it ).lower() )
                || m_url.url().lower().contains( ( *it ).lower() ) )
            hits++;
        else {
            QStringList::const_iterator tit;
            for ( tit = m_tags.begin(); tit != m_tags.end(); ++tit ) {
                if ( ( *tit ).lower().contains( ( *it ).lower() ) ) {
                    hits++;
                    break;
                }
            }
        }
    }
    return hits == ct;
}

QDataStream &operator<<( QDataStream& s, const Post& post )
{
    s << post.m_title << post.m_description << post.m_url << post.m_tags << post.m_user;
    return s;
}

QDataStream &operator>>( QDataStream& s, Post& post )
{
    s >> post.m_title >> post.m_description >> post.m_url >> post.m_tags >> post.m_user;
    return s;
}

#include "bookmarkapi.moc"
